From fa8b71450c764bb0d4d3514c8372637ea3c64c35 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 7 Nov 2012 14:32:35 +0100 Subject: [PATCH] Add gtk_widget_get/set_opacity This adds gtk_widget_get/set_opacity, as well as a GtkWidget.opacity property. Additionally it deprectates gtk_window_get/set_opacity and removes the GtkWindow.opacity property (in preference for the new identical inherited property from GtkWidget, which should be ABI/API compat). The implementation is using the new gdk_window_set_opacity child window support for windowed widgets, and cairo_push/pop_group() bracketing in gtk_widget_draw() for non-window widgets. https://bugzilla.gnome.org/show_bug.cgi?id=687842 --- gtk/gtk.symbols | 2 + gtk/gtkwidget.c | 259 ++++++++++++++++++++++++++++++++++++++++++++++++ gtk/gtkwidget.h | 5 + gtk/gtkwindow.c | 54 +--------- gtk/gtkwindow.h | 2 + 5 files changed, 272 insertions(+), 50 deletions(-) diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 9f0d61a8a1..d0e219c643 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -3720,6 +3720,7 @@ gtk_widget_get_modifier_style gtk_widget_get_name gtk_widget_get_no_show_all gtk_widget_get_pango_context +gtk_widget_get_opacity gtk_widget_get_parent gtk_widget_get_parent_window gtk_widget_get_path @@ -3874,6 +3875,7 @@ gtk_widget_set_margin_right gtk_widget_set_margin_top gtk_widget_set_name gtk_widget_set_no_show_all +gtk_widget_set_opacity gtk_widget_set_parent gtk_widget_set_parent_window gtk_widget_set_realized diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 2118c43d94..a2faff55e1 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -357,6 +358,11 @@ struct _GtkWidgetPrivate /* SizeGroup related flags */ guint have_size_groups : 1; + guint norender_children : 1; + guint norender : 1; /* Don't expose windows, instead recurse via draw */ + + guint8 alpha; + /* The widget's name. If the widget does not have a name * (the name is NULL), then its name (as returned by * "gtk_widget_get_name") is its class's name. @@ -508,6 +514,7 @@ enum { PROP_TOOLTIP_MARKUP, PROP_TOOLTIP_TEXT, PROP_WINDOW, + PROP_OPACITY, PROP_DOUBLE_BUFFERED, PROP_HALIGN, PROP_VALIGN, @@ -700,6 +707,9 @@ static void gtk_widget_set_device_enabled_internal (GtkWidget *widget, gboolean recurse, gboolean enabled); static gboolean event_window_is_still_viewable (GdkEvent *event); +static void gtk_cairo_set_event (cairo_t *cr, + GdkEventExpose *event); +static void gtk_widget_update_norender (GtkWidget *widget); /* --- variables --- */ static gpointer gtk_widget_parent_class = NULL; @@ -809,9 +819,23 @@ gtk_widget_draw_marshaller (GClosure *closure, gpointer invocation_hint, gpointer marshal_data) { + GtkWidget *widget = g_value_get_object (¶m_values[0]); + GdkEventExpose *tmp_event; + gboolean push_group; cairo_t *cr = g_value_get_boxed (¶m_values[1]); cairo_save (cr); + tmp_event = _gtk_cairo_get_event (cr); + + push_group = + widget->priv->alpha != 255 && + (!gtk_widget_get_has_window (widget) || tmp_event == NULL); + + if (push_group) + { + cairo_push_group (cr); + gtk_cairo_set_event (cr, NULL); + } _gtk_marshal_BOOLEAN__BOXED (closure, return_value, @@ -820,6 +844,15 @@ gtk_widget_draw_marshaller (GClosure *closure, invocation_hint, marshal_data); + + if (push_group) + { + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_paint_with_alpha (cr, widget->priv->alpha / 255.0); + } + + gtk_cairo_set_event (cr, tmp_event); cairo_restore (cr); } @@ -832,6 +865,9 @@ gtk_widget_draw_marshallerv (GClosure *closure, int n_params, GType *param_types) { + GtkWidget *widget = GTK_WIDGET (instance); + GdkEventExpose *tmp_event; + gboolean push_group; cairo_t *cr; va_list args_copy; @@ -839,6 +875,17 @@ gtk_widget_draw_marshallerv (GClosure *closure, cr = va_arg (args_copy, gpointer); cairo_save (cr); + tmp_event = _gtk_cairo_get_event (cr); + + push_group = + widget->priv->alpha != 255 && + (!gtk_widget_get_has_window (widget) || tmp_event == NULL); + + if (push_group) + { + cairo_push_group (cr); + gtk_cairo_set_event (cr, NULL); + } _gtk_marshal_BOOLEAN__BOXEDv (closure, return_value, @@ -848,6 +895,15 @@ gtk_widget_draw_marshallerv (GClosure *closure, n_params, param_types); + + if (push_group) + { + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_paint_with_alpha (cr, widget->priv->alpha / 255.0); + } + + gtk_cairo_set_event (cr, tmp_event); cairo_restore (cr); va_end (args_copy); @@ -1420,6 +1476,25 @@ gtk_widget_class_init (GtkWidgetClass *klass) FALSE, GTK_PARAM_READWRITE)); + /** + * GtkWidget:opacity: + * + * The requested opacity of the widget. See gtk_widget_set_opacity() for + * more details about window opacity. + * + * Before 3.8 this was only availible in GtkWindow + * + * Since: 3.8 + */ + g_object_class_install_property (gobject_class, + PROP_OPACITY, + g_param_spec_double ("opacity", + P_("Opacity for Widget"), + P_("The opacity of the widget, from 0 to 1"), + 0.0, + 1.0, + 1.0, + GTK_PARAM_READWRITE)); /** * GtkWidget::show: * @widget: the object which received the signal. @@ -3484,6 +3559,9 @@ gtk_widget_set_property (GObject *object, gtk_widget_set_vexpand (widget, g_value_get_boolean (value)); g_object_thaw_notify (G_OBJECT (widget)); break; + case PROP_OPACITY: + gtk_widget_set_opacity (widget, g_value_get_double (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -3640,6 +3718,9 @@ gtk_widget_get_property (GObject *object, gtk_widget_get_hexpand (widget) && gtk_widget_get_vexpand (widget)); break; + case PROP_OPACITY: + g_value_set_double (value, gtk_widget_get_opacity (widget)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -3662,6 +3743,7 @@ gtk_widget_init (GtkWidget *widget) priv->allocation.y = -1; priv->allocation.width = 1; priv->allocation.height = 1; + priv->alpha = 255; priv->window = NULL; priv->parent = NULL; @@ -3952,6 +4034,8 @@ gtk_widget_unparent (GtkWidget *widget) g_object_notify_queue_clear (G_OBJECT (widget), nqueue); g_object_notify_queue_thaw (G_OBJECT (widget), nqueue); + gtk_widget_update_norender (widget); + gtk_widget_pop_verify_invariants (widget); g_object_unref (widget); } @@ -8079,6 +8163,8 @@ gtk_widget_set_parent (GtkWidget *widget, gtk_widget_queue_compute_expand (parent); } + gtk_widget_update_norender (widget); + gtk_widget_pop_verify_invariants (widget); } @@ -13634,6 +13720,11 @@ gtk_widget_set_window (GtkWidget *widget, if (priv->window != window) { priv->window = window; + + if (gtk_widget_get_has_window (widget) && window != NULL && !gdk_window_has_native (window)) + gdk_window_set_opacity (window, + priv->norender ? 0 : priv->alpha / 255.0); + g_object_notify (G_OBJECT (widget), "window"); } } @@ -13671,6 +13762,10 @@ gtk_widget_register_window (GtkWidget *widget, gdk_window_set_user_data (window, widget); priv->registered_windows = g_list_prepend (priv->registered_windows, window); + + if (!gtk_widget_get_has_window (widget) && !gdk_window_has_native (window)) + gdk_window_set_opacity (window, + (priv->norender || priv->norender_children) ? 0.0 : 1.0); } /** @@ -13764,6 +13859,170 @@ gtk_widget_set_support_multidevice (GtkWidget *widget, gdk_window_set_support_multidevice (priv->window, support_multidevice); } +static void apply_norender (GtkWidget *widget, gboolean norender); + +static void +apply_norender_cb (GtkWidget *widget, gpointer norender) +{ + apply_norender (widget, GPOINTER_TO_INT (norender)); +} + +static void +propagate_norender_non_window (GtkWidget *widget, gboolean norender) +{ + GList *l; + + g_assert (!gtk_widget_get_has_window (widget)); + + for (l = widget->priv->registered_windows; l != NULL; l = l->next) + gdk_window_set_opacity (l->data, + norender ? 0 : widget->priv->alpha / 255.0); + + if (GTK_IS_CONTAINER (widget)) + gtk_container_forall (GTK_CONTAINER (widget), apply_norender_cb, + GINT_TO_POINTER (norender)); +} + +static void +apply_norender (GtkWidget *widget, gboolean norender) +{ + if (widget->priv->norender == norender) + return; + + widget->priv->norender = norender; + + if (gtk_widget_get_has_window (widget)) + { + if (widget->priv->window != NULL) + gdk_window_set_opacity (widget->priv->window, + norender ? 0 : widget->priv->alpha / 255.0); + } + else + propagate_norender_non_window (widget, norender | widget->priv->norender_children); +} + +/* This is called when the norender_children state of a non-window widget changes, + * its parent changes, or its has_window state changes. It means we need + * to update the norender of all the windows owned by the widget and those + * of child widgets, up to and including the first windowed widgets in the hierarchy. + */ +static void +gtk_widget_update_norender (GtkWidget *widget) +{ + gboolean norender; + GtkWidget *parent; + + parent = widget->priv->parent; + + norender = + parent != NULL && + (parent->priv->norender_children || + (parent->priv->norender && !gtk_widget_get_has_window (parent))); + + apply_norender (widget, norender); + + /* The above may not have propagated to children if norender_children changed but + not norender, so we need to enforce propagation. */ + if (!gtk_widget_get_has_window (widget)) + propagate_norender_non_window (widget, norender | widget->priv->norender_children); +} + +/** + * gtk_widget_set_opacity: + * @widget: a #GtkWidget + * @opacity: desired opacity, between 0 and 1 + * + * Request the @widget to be rendered partially transparent, + * with opacity 0 being fully transparent and 1 fully opaque. (Opacity values + * are clamped to the [0,1] range.). + * This works on both toplevel widget, and child widgets, although there + * are some limitations: + * + * For toplevel widgets this depends on the capabilities of the windowing + * system. On X11 this has any effect only on X screens with a compositing manager + * running. See gtk_widget_is_composited(). On Windows it should work + * always, although setting a window's opacity after the window has been + * shown causes it to flicker once on Windows. + * + * For child widgets it doesn't work if any affected widget has a native window, or + * disables double buffering. + * + * Since: 3.8 + **/ +void +gtk_widget_set_opacity (GtkWidget *widget, + gdouble opacity) +{ + GtkWidgetPrivate *priv; + guint8 alpha; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = widget->priv; + + if (opacity < 0.0) + opacity = 0.0; + else if (opacity > 1.0) + opacity = 1.0; + + alpha = round (opacity * 255); + if (alpha == priv->alpha) + return; + + priv->alpha = alpha; + + if (gtk_widget_get_has_window (widget)) + { + if (priv->window != NULL) + gdk_window_set_opacity (priv->window, + priv->norender ? 0 : opacity); + } + else + { + /* For non windowed widgets we can't use gdk_window_set_opacity() directly, as there is + no GdkWindow at the right place in the hierarchy. For no-window widget this is not a problem, + as we just push an opacity group in the draw marshaller. + + However, that only works for non-window descendant widgets. If any descendant has a + window that window will not normally be rendered in the draw signal, so the opacity + group will not work for it. + + To fix this we set all such windows to a zero opacity, meaning they don't get drawn + by gdk, and instead we set a NULL _gtk_cairo_get_event during expose so that the draw + handler recurses into windowed widgets. + + We do this by setting "norender_children", which means that any windows in this widget + or its ancestors (stopping at the first such windows at each branch in the hierarchy) + are set to zero opacity. This is then propagated into all necessary children as norender, + which controls whether a window should be exposed or not. + */ + priv->norender_children = priv->alpha != 255; + gtk_widget_update_norender (widget); + } + + if (gtk_widget_get_realized (widget)) + gtk_widget_queue_draw (widget); +} + +/** + * gtk_widget_get_opacity: + * @widget: a #GtkWidget + * + * Fetches the requested opacity for this widget. See + * gtk_widget_set_opacity(). + * + * Return value: the requested opacity for this widget. + * + * Since: 3.8 + **/ +gdouble +gtk_widget_get_opacity (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0.0); + + return widget->priv->alpha / 255.0; +} + static void _gtk_widget_set_has_focus (GtkWidget *widget, gboolean has_focus) diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index ff4f417bc5..a4c20e86c2 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -670,6 +670,11 @@ void gtk_widget_set_device_events (GtkWidget *widget, void gtk_widget_add_device_events (GtkWidget *widget, GdkDevice *device, GdkEventMask events); +GDK_AVAILABLE_IN_3_8 +void gtk_widget_set_opacity (GtkWidget *widget, + double opacity); +GDK_AVAILABLE_IN_3_8 +double gtk_widget_get_opacity (GtkWidget *widget); void gtk_widget_set_device_enabled (GtkWidget *widget, GdkDevice *device, diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index 6fc82f8311..a2f9b18e0d 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -118,8 +118,6 @@ struct _GtkWindowPrivate GdkModifierType mnemonic_modifier; GdkWindowTypeHint gdk_type_hint; - gdouble opacity; - GdkWindow *grip_window; gchar *startup_id; @@ -166,7 +164,6 @@ struct _GtkWindowPrivate guint mnemonics_visible_set : 1; guint focus_visible : 1; guint modal : 1; - guint opacity_set : 1; guint position : 3; guint reset_type_hint : 1; guint resizable : 1; @@ -228,7 +225,6 @@ enum { PROP_GRAVITY, PROP_TRANSIENT_FOR, PROP_ATTACHED_TO, - PROP_OPACITY, PROP_HAS_RESIZE_GRIP, PROP_RESIZE_GRIP_VISIBLE, PROP_APPLICATION, @@ -983,24 +979,6 @@ gtk_window_class_init (GtkWindowClass *klass) GTK_TYPE_WIDGET, GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - /** - * GtkWindow:opacity: - * - * The requested opacity of the window. See gtk_window_set_opacity() for - * more details about window opacity. - * - * Since: 2.12 - */ - g_object_class_install_property (gobject_class, - PROP_OPACITY, - g_param_spec_double ("opacity", - P_("Opacity for Window"), - P_("The opacity of the window, from 0 to 1"), - 0.0, - 1.0, - 1.0, - GTK_PARAM_READWRITE)); - /* Style properties. */ gtk_widget_class_install_style_property (widget_class, @@ -1178,7 +1156,6 @@ gtk_window_init (GtkWindow *window) priv->focus_on_map = TRUE; priv->deletable = TRUE; priv->type_hint = GDK_WINDOW_TYPE_HINT_NORMAL; - priv->opacity = 1.0; priv->startup_id = NULL; priv->initial_timestamp = GDK_CURRENT_TIME; priv->has_resize_grip = TRUE; @@ -1300,9 +1277,6 @@ gtk_window_set_property (GObject *object, case PROP_ATTACHED_TO: gtk_window_set_attached_to (window, g_value_get_object (value)); break; - case PROP_OPACITY: - gtk_window_set_opacity (window, g_value_get_double (value)); - break; case PROP_HAS_RESIZE_GRIP: gtk_window_set_has_resize_grip (window, g_value_get_boolean (value)); break; @@ -1424,9 +1398,6 @@ gtk_window_get_property (GObject *object, case PROP_ATTACHED_TO: g_value_set_object (value, gtk_window_get_attached_to (window)); break; - case PROP_OPACITY: - g_value_set_double (value, gtk_window_get_opacity (window)); - break; case PROP_HAS_RESIZE_GRIP: g_value_set_boolean (value, priv->has_resize_grip); break; @@ -2707,28 +2678,13 @@ gtk_window_get_attached_to (GtkWindow *window) * shown causes it to flicker once on Windows. * * Since: 2.12 + * Deprecated: 3.8: Use gtk_widget_set_opacity instead. **/ void gtk_window_set_opacity (GtkWindow *window, gdouble opacity) { - GtkWindowPrivate *priv; - - g_return_if_fail (GTK_IS_WINDOW (window)); - - priv = window->priv; - - if (opacity < 0.0) - opacity = 0.0; - else if (opacity > 1.0) - opacity = 1.0; - - priv->opacity_set = TRUE; - priv->opacity = opacity; - - if (gtk_widget_get_realized (GTK_WIDGET (window))) - gdk_window_set_opacity (gtk_widget_get_window (GTK_WIDGET (window)), - priv->opacity); + gtk_widget_set_opacity (GTK_WIDGET (window), opacity); } /** @@ -2741,13 +2697,14 @@ gtk_window_set_opacity (GtkWindow *window, * Return value: the requested opacity for this window. * * Since: 2.12 + * Deprecated: 3.8: Use gtk_widget_get_opacity instead. **/ gdouble gtk_window_get_opacity (GtkWindow *window) { g_return_val_if_fail (GTK_IS_WINDOW (window), 0.0); - return window->priv->opacity; + return gtk_widget_get_opacity (GTK_WIDGET (window)); } /** @@ -5281,9 +5238,6 @@ gtk_window_realize (GtkWidget *widget) gdk_window = gdk_window_new (parent_window, &attributes, attributes_mask); gtk_widget_set_window (widget, gdk_window); - if (priv->opacity_set) - gdk_window_set_opacity (gdk_window, priv->opacity); - gdk_window_enable_synchronized_configure (gdk_window); gtk_widget_register_window (widget, gdk_window); diff --git a/gtk/gtkwindow.h b/gtk/gtkwindow.h index 22197b2312..7b0c0dc5e5 100644 --- a/gtk/gtkwindow.h +++ b/gtk/gtkwindow.h @@ -138,8 +138,10 @@ void gtk_window_set_attached_to (GtkWindow *window, GtkWidget *attach_widget); GDK_AVAILABLE_IN_3_4 GtkWidget *gtk_window_get_attached_to (GtkWindow *window); +GDK_DEPRECATED_IN_3_8_FOR(gtk_widget_set_opacity) void gtk_window_set_opacity (GtkWindow *window, gdouble opacity); +GDK_DEPRECATED_IN_3_8_FOR(gtk_widget_get_opacity) gdouble gtk_window_get_opacity (GtkWindow *window); void gtk_window_set_type_hint (GtkWindow *window, GdkWindowTypeHint hint); -- 2.30.2